package capsolver_go

import (
	"context"
	"gitee.com/kaylee595/gkaylee/kerr"
	"github.com/imroc/req/v3"
	"os"
)

type Solver struct {
	httpClient  *req.Client
	apiKey      string
	apiEndpoint *Endpoint
	appid       string
	callbackUrl string
}

type Option func(*Solver)

func WithHttpClient(httpClient *req.Client) Option {
	return func(c *Solver) {
		c.httpClient = adjustClient(httpClient.Clone())
	}
}

func WithApiKey(apiKey string) Option {
	return func(c *Solver) {
		c.apiKey = apiKey
	}
}

func WithApiEndpoint(apiEndpoint *Endpoint) Option {
	return func(c *Solver) {
		c.apiEndpoint = apiEndpoint
	}
}

func WithAppid(appid string) Option {
	return func(c *Solver) {
		c.appid = appid
	}
}

func WithCallbackUrl(callbackUrl string) Option {
	return func(c *Solver) {
		c.callbackUrl = callbackUrl
	}
}

var _, _, _, _, _ = WithHttpClient, WithApiKey, WithApiEndpoint, WithAppid, WithCallbackUrl

func New(opts ...Option) *Solver {
	c := &Solver{httpClient: adjustClient(req.C()), apiEndpoint: ApiHostFromEnvOrDefault}
	for _, opt := range opts {
		opt(c)
	}
	return c
}

func adjustClient(c *req.Client) *req.Client {
	return c.OnAfterResponse(checkError)
}

func (s *Solver) GetHttpClient() *req.Client {
	return s.httpClient
}

var ApiKeyFromEnv = os.Getenv("CAPSOLVER_API_KEY")

func (s *Solver) GetApiKey() string {
	if s.apiKey == "" {
		return ApiKeyFromEnv
	}
	return s.apiKey
}

type RespBalance struct {
	Balance  float64              `json:"balance"`  // Account balance value in USD
	Packages []RespBalancePackage `json:"packages"` // Monthly/Weekly Packages
}

type RespBalancePackage struct {
	PackageId     string `json:"packageId"`
	Type          int    `json:"type"`
	Title         string `json:"title"`
	NumberOfCalls int    `json:"numberOfCalls"`
	Status        int    `json:"status"`
	Token         string `json:"token"`
	ExpireTime    int    `json:"expireTime"`
}

// GetBalance https://docs.capsolver.com/en/guide/api-getbalance/
func (s *Solver) GetBalance(ctx context.Context) (*RespBalance, error) {
	const uri = "/getBalance"
	reqBody := map[string]string{
		"clientKey": s.GetApiKey(),
	}
	ret := new(RespBalance)
	resp := s.httpClient.Post(s.apiEndpoint.JoinPath(uri).String()).SetBodyJsonMarshal(reqBody).SetSuccessResult(ret).Do(ctx)
	if resp.Err != nil {
		return nil, kerr.WrapTraceStack(resp.Err)
	}
	return ret, nil
}

type Task map[string]any

type RespCreateTask struct {
	TaskId   string         `json:"taskId"`
	Status   TaskStatus     `json:"status"`
	Solution map[string]any `json:"solution"`
}

// CreateTask https://docs.capsolver.com/en/guide/api-createtask/
func (s *Solver) CreateTask(ctx context.Context, task Task) (*RespCreateTask, error) {
	const uri = "/createTask"
	reqBody := map[string]any{
		"clientKey": s.GetApiKey(),
		"task":      task,
	}
	if s.appid != "" {
		reqBody["appid"] = s.appid
	}
	if s.callbackUrl != "" {
		reqBody["callbackUrl"] = s.callbackUrl
	}
	ret := new(RespCreateTask)
	resp := s.httpClient.Post(s.apiEndpoint.JoinPath(uri).String()).SetBodyJsonMarshal(reqBody).SetSuccessResult(ret).Do(ctx)
	if resp.Err != nil {
		return nil, kerr.WrapTraceStack(resp.Err)
	}
	return ret, nil
}

type RespGetTaskResult[T any] struct {
	TaskId   string     `json:"taskId"`
	Solution T          `json:"solution"`
	Status   TaskStatus `json:"status"`
}

// GetTaskResult https://docs.capsolver.com/en/guide/api-gettaskresult/
func (s *Solver) GetTaskResult(ctx context.Context, taskId string) (*RespGetTaskResult[map[string]any], error) {
	const uri = "/getTaskResult"
	reqBody := map[string]string{
		"clientKey": s.GetApiKey(),
		"taskId":    taskId,
	}
	ret := new(RespGetTaskResult[map[string]any])
	resp := s.httpClient.Post(s.apiEndpoint.JoinPath(uri).String()).SetBodyJsonMarshal(reqBody).SetSuccessResult(ret).Do(ctx)
	if resp.Err != nil {
		return nil, kerr.WrapTraceStack(resp.Err)
	}
	return ret, nil
}

type RespGetToken = RespCreateTask

// GetToken https://docs.capsolver.com/en/guide/api-getToken/
// maybe only support in recaptcha
func (s *Solver) GetToken(ctx context.Context, task Task) (*RespGetToken, error) {
	const uri = "/getToken"
	reqBody := map[string]any{
		"clientKey": s.GetApiKey(),
		"task":      task,
	}
	if s.appid != "" {
		reqBody["appid"] = s.appid
	}
	if s.callbackUrl != "" {
		reqBody["callbackUrl"] = s.callbackUrl
	}
	ret := new(RespCreateTask)
	resp := s.httpClient.Post(s.apiEndpoint.JoinPath(uri).String()).SetBodyJsonMarshal(reqBody).SetSuccessResult(ret).Do(ctx)
	if resp.Err != nil {
		return nil, kerr.WrapTraceStack(resp.Err)
	}
	return ret, nil
}

type TaskType string

const (
	TaskTypeGeeTestTaskProxyLess               TaskType = "GeeTestTaskProxyLess"
	TaskTypeReCaptchaV2Task                    TaskType = "ReCaptchaV2Task"
	TaskTypeReCaptchaV2EnterpriseTask          TaskType = "ReCaptchaV2EnterpriseTask"
	TaskTypeReCaptchaV2TaskProxyLess           TaskType = "ReCaptchaV2TaskProxyLess"
	TaskTypeReCaptchaV2EnterpriseTaskProxyLess TaskType = "ReCaptchaV2EnterpriseTaskProxyLess"
	TaskTypeReCaptchaV3Task                    TaskType = "ReCaptchaV3Task"
	TaskTypeReCaptchaV3EnterpriseTask          TaskType = "ReCaptchaV3EnterpriseTask"
	TaskTypeReCaptchaV3TaskProxyLess           TaskType = "ReCaptchaV3TaskProxyLess"
	TaskTypeReCaptchaV3EnterpriseTaskProxyLess TaskType = "ReCaptchaV3EnterpriseTaskProxyLess"

	_, _, _, _, _, _, _, _, _ = TaskTypeGeeTestTaskProxyLess, TaskTypeReCaptchaV2Task,
		TaskTypeReCaptchaV2EnterpriseTask, TaskTypeReCaptchaV2TaskProxyLess, TaskTypeReCaptchaV2EnterpriseTaskProxyLess,
		TaskTypeReCaptchaV3Task, TaskTypeReCaptchaV3EnterpriseTask, TaskTypeReCaptchaV3TaskProxyLess, TaskTypeReCaptchaV3EnterpriseTaskProxyLess
)
